#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package}.api.config.swagger;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Configuration
@EnableSwagger2
@EnableKnife4j
@ConditionalOnProperty(name = "swagger.enabled")
public class Swagger2Configuration {

    /**
     * 默认的排除路径，排除Spring Boot默认的错误处理路径和端点
     */
    public static final List<String> DEFAULT_EXCLUDE_PATH = Arrays.asList("/error", "/actuator/**");
    public static final String BASE_PATH = "/**";

    private static final String YOUR_API_KEY_HEADER = "your-api-key-header";

    private static final String HEADER = "header";
    private static final String QUERY = "query";

    @Bean
    @ConditionalOnMissingBean
    public Swagger2Properties swagger2Properties() {
        return new Swagger2Properties();
    }

    @Bean
    @ConditionalOnMissingBean
    public Docket buildDocket(Swagger2Properties swagger2Properties) {
        // base-path处理
        if (swagger2Properties.getBasePath().isEmpty()) {
            swagger2Properties.getBasePath().add(BASE_PATH);
        }
        List<Predicate<String>> basePath = new ArrayList<>();
        swagger2Properties.getBasePath().forEach(path -> basePath.add(PathSelectors.ant(path)));

        // exclude-path处理
        if (swagger2Properties.getExcludePath().isEmpty()) {
            swagger2Properties.getExcludePath().addAll(DEFAULT_EXCLUDE_PATH);
        }
        List<Predicate<String>> excludePath = new ArrayList<>();
        swagger2Properties.getExcludePath().forEach(path -> excludePath.add(PathSelectors.ant(path)));

        //noinspection Guava
        return new Docket(DocumentationType.SWAGGER_2)
                .enable(swagger2Properties.getEnabled())
                .host(swagger2Properties.getHost())
                .apiInfo(apiInfo(swagger2Properties)).select()
                .apis(RequestHandlerSelectors.basePackage(swagger2Properties.getBasePackage()))
                .paths(Predicates.and(Predicates.not(Predicates.or(excludePath)), Predicates.or(basePath)))
                .build()
                .securitySchemes(securitySchemas())
                .securityContexts(Collections.singletonList(securityContext()))
                .pathMapping("/");
    }

    private ApiInfo apiInfo(Swagger2Properties swagger2Properties) {
        return new ApiInfoBuilder()
                .title(swagger2Properties.getTitle())
                .description(swagger2Properties.getDescription())
                .license(swagger2Properties.getLicense())
                .licenseUrl(swagger2Properties.getLicenseUrl())
                .termsOfServiceUrl(swagger2Properties.getTermsOfServiceUrl())
                .contact(new Contact(
                        swagger2Properties.getContact().getName(),
                        swagger2Properties.getContact().getUrl(),
                        swagger2Properties.getContact().getEmail()))
                .version(swagger2Properties.getVersion())
                .build();
    }

    /**
     * 配置默认的全局鉴权策略的开关，通过正则表达式进行匹配；默认匹配所有URL
     */
    protected SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(defaultAuthReferences())
                .forPaths(PathSelectors.regex(swagger2Properties().getAuthorization().getAuthRegex()))
                .build();
    }

    protected List<SecurityScheme> securitySchemas() {
        List<SecurityScheme> result = new ArrayList<>(apiKeySecuritySchemes());
        result.add(oauthSecuritySchema());
        return result;
    }

    protected List<SecurityReference> defaultAuthReferences() {
        List<SecurityReference> result = new ArrayList<>();
        result.addAll(oauthAuth());

        result.addAll(Lists.newArrayList(
                apiKeySecurityReference(YOUR_API_KEY_HEADER)
                // add more api key
        ));
        return result;
    }

    /**
     * 默认的全局鉴权策略
     */
    protected List<SecurityReference> oauthAuth() {
        ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swagger2Properties().getAuthorization()
                .getAuthorizationScopeList()
                .forEach(authorizationScope ->
                        authorizationScopeList.add(
                                new AuthorizationScope(
                                        authorizationScope.getScope(),
                                        authorizationScope.getDescription()
                                )));

        AuthorizationScope[] authorizationScopes = new AuthorizationScope[authorizationScopeList.size()];
        return Collections.singletonList(
                SecurityReference
                        .builder()
                        .reference(swagger2Properties().getAuthorization().getName())
                        .scopes(authorizationScopeList.toArray(authorizationScopes))
                        .build());
    }

    protected OAuth oauthSecuritySchema() {
        ArrayList<AuthorizationScope> authorizationScopeList = new ArrayList<>();
        swagger2Properties().getAuthorization()
                .getAuthorizationScopeList()
                .forEach(authorizationScope ->
                        authorizationScopeList.add(
                                new AuthorizationScope(
                                        authorizationScope.getScope(),
                                        authorizationScope.getDescription()
                                )));

        ArrayList<GrantType> grantTypes = new ArrayList<>();
        swagger2Properties().getAuthorization()
                .getTokenUrlList()
                .forEach(tokenUrl ->
                        grantTypes.add(
                                new ResourceOwnerPasswordCredentialsGrant(tokenUrl)
                        ));
        return new OAuth(swagger2Properties().getAuthorization().getName(), authorizationScopeList, grantTypes);
    }

    private List<ApiKey> apiKeySecuritySchemes() {
        return Lists.newArrayList(
                new ApiKey(YOUR_API_KEY_HEADER, YOUR_API_KEY_HEADER, HEADER)
                // add more api key
        );
    }

    private SecurityReference apiKeySecurityReference(String key) {
        return
                SecurityReference
                        .builder()
                        .reference(key)
                        .scopes(new AuthorizationScope[]{new AuthorizationScope(key, key)})
                        .build();
    }

}
